//:  http_parser.obj httparser.d \console

import 
	tango.io.Stdout,
	
	Int	= tango.text.convert.Integer,
	Utf	= tango.text.convert.Utf,
	
	tango.stdc.string,
	tango.stdc.stringz,
	tango.core.Thread , 
	tango.core.Array , 
	tango.core.sync.Atomic,
	tango.core.Traits,
	tango.io.device.Array,
	tango.io.device.File,
	tango.net.InternetAddress,
	tango.net.device.Berkeley,
	 tango.net.http.HttpConst,
	httparser;
	
	
void main() {
	auto addr	= new InternetAddress(8888);
	auto hs	= HttpServer.Init(addr, 6 ,  function(HttpReq* req, HttpRes* res) {
		log!(__FILE__, __LINE__)("  {}", req.getPath ) ;
		res.ob("123\n")
			( req.getPath )("\n")
			( req.getQueryString )("\n")
			( req.getUserAgent )("\n")
		;
		res.send;
	}) ;
	hs.start ;
}

void log(char[] file, int line, T...)(T t){
	synchronized {
		Thread th	= Thread.getThis;
		Stdout.format("({}:{}:{:x}) ", file, line, cast(void*)th) ;
		Stdout.formatln(t).flush;
	}
}

struct HttpServer {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;
	alias void function(HttpReq*, HttpRes*) CallBack ;
	
	Berkeley	ssoc ;
	Address	addr ;
	Thread[]	pool ;
	bool		_isWorking ;
	CallBack	cb ;
	
	static pThis Init(Address addr, int woker_number, CallBack cb){
		pThis _this	= new This ;
		_this.pool.length	= woker_number ;
		for(int i = 0 ; i < woker_number ; i++ ) {
			_this.pool[i]	= new Thread(&_this.worker);
		}
		_this.addr	= addr ;
		_this.cb	= cb ;
		return _this ;
	}
	
	bool isWorking(){
		return atomicLoad(_isWorking) ;
	}
	
	pThis isWorking(bool val){
		atomicStore(_isWorking, val);
		return this ;
	}
	
	pThis start(){
		ssoc.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
		ssoc.bind(addr);
		ssoc.listen(32);
		isWorking(true);
		foreach( th; pool){
			th.start;
		}
		return this ;
	}
	
	pThis stop(){
		isWorking(false);
		foreach(ref th;pool){
			th.join ;
		}
		return this ;
	}
	
	void worker() {
		Thread th	= Thread.getThis;
		// log!(__FILE__, __LINE__)("start") ;
		HttpReq	req ;
		HttpRes	res ;
		Berkeley	soc ;
		req.init(&soc) ;
		res.init(&soc) ;
		
		bool	isNeedAccept	= true ;
		while(  isWorking ) {
			
			req.reset ;
			res.reset ;
			
			if( isNeedAccept ) {
				ssoc.accept(soc) ;
				log!(__FILE__, __LINE__)(" accept: {:x}", cast(void*) soc.handle ) ;
				isNeedAccept	= false ;
			}
			
			req.parser() ;
			if( req.isLegal ){
				cb(&req, &res);
			}
			
			if( !req.isLegal ){
				soc.shutdown(SocketShutdown.BOTH);
				isNeedAccept	= true ;
			}else if( !soc.isAlive ){
				log!(__FILE__, __LINE__)(" closed " ) ;
				isNeedAccept	= true ;
			}
			
		}
	}
	
}

static const char[][] HttpReq_Keys = [
	"Path",
	"Url",
	"QueryString",
	"Fragment",
	"Host",
	"User-Agent",
	"Accept-Language",
	"Accept-Encoding",
	"Accept-Charset",
	"Accept",
	"Keep-Alive",
	"Connection",
	"Cookie",
] ;

struct HttpReq_Value {
	int	i ;
	int	l ;
}

char[] _GenIKey( char[][]  keys){
	char[] code	= "enum {" ;
	foreach( int i , key ; keys ){
		char[] _key ;
		foreach( c; key ) if ( c !is '-')  _key ~= c  ;
		code	~= "_i" ~ _key ~ "," ;
	}
	code	~= "}\n" ;
	foreach( int i , key ; keys ) {
		char[] _key ;
		foreach( c; key ) if ( c !is '-') _key ~= c  ;
		code	~= "char[] get" ~ _key ~ "(){ HttpReq_Value* v = &_Values[_i" ~  _key ~ "] ;  char* ptr = cast(char*) &ih.slice[v.i]; return ptr[0 .. v.l]; \n}\n" ;
	}
	return code ;
}

struct HttpReq {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;

	http_parser 	_parser ;
	http_cb		on_message_begin	= cast(http_cb) &_on_message_begin ;
	http_data_cb	on_path			= cast(http_data_cb) &_on_path ;
	http_data_cb	on_query_string		= cast(http_data_cb)&_on_query_string ;
	http_data_cb	on_url			= cast(http_data_cb)&_on_url ;
	http_data_cb	on_fragment		= cast(http_data_cb)&_on_fragment ;
	http_data_cb	on_header_field		= cast(http_data_cb)&_on_header_field ;
	http_data_cb	on_header_value	= cast(http_data_cb)&_on_header_value ;
	http_cb		on_headers_complete	= cast(http_cb)&_on_headers_complete ;
	http_data_cb	on_body			= cast(http_data_cb)&_on_body ;
	http_cb		on_message_complete	= cast(http_cb)&_on_message_complete ;
	
	Berkeley*	soc ;
	Array	 	ih, ib ;
	bool		isLegal  ;
	bool		_last_header_was_a_value ;
	bool		_headers_complete ;
	
	HttpReq_Value	_header_key , _header_val ;

	static const Len	= HttpReq_Keys.length ;
	HttpReq_Value[Len]	_Values ;
	mixin( _GenIKey(HttpReq_Keys) ) ;

	
	void init(Berkeley* _soc){
		soc	= _soc ;
		ih	= new Array(1024, 1024) ;
		ib	= new Array(1024 * 16 , 1024 * 32) ;
		http_parser_init(&_parser, 0);
	}
	
	http_parser_settings* callback(){
		return cast(http_parser_settings*) ((cast(uint) this) + on_message_begin.offsetof );
	}
	
	void parser() {
		char[1024] tmp ;
		while( true ){
			int i	= soc.receive(tmp);
			if( i < 1 || i > tmp.length ){
				break;
			}
			int ret;
			if( _headers_complete ){
				ret = http_parser_execute(&_parser, callback, &tmp[0], i) ;
			} else {
				ih(tmp[0..i]);
				ret = http_parser_execute(&_parser, callback, cast(char*)&ih.slice[$-i] , i) ;
			}
			if( ret != i || isLegal ) {
				break ;
			}
			log!(__FILE__,__LINE__)("stat = {} ",   Stat[_parser.header_state] );
		}
	}
	
	void reset(){
		memset(&_Values, 0,  HttpReq_Value.sizeof * _Values.length );
		memset(&_header_key, 0,  HttpReq_Value.sizeof  );
		memset(&_header_val, 0,  HttpReq_Value.sizeof  );
		
		isLegal	= false ;
		_headers_complete	= false ;
		_last_header_was_a_value	= false ;
		ih.clear;
		ib.clear;
	}
	
	static extern(C) int _on_message_begin(pThis _this ){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("message_begin") ;
		return 0 ;
	}
	
	static extern(C) int _on_path(pThis _this , char* da, size_t len){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("path = {} ", da[0..len]) ;
		HttpReq_Value* v	= &_this._Values[ _iPath ] ;
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		return 0 ;
	}

	static extern(C) int _on_query_string(pThis _this , char* da, size_t len){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("query_string = {}", da[0..len]) ;
		HttpReq_Value* v	= &_this._Values[ _iQueryString ] ;
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		return 0 ;
	}
	
	static extern(C) int _on_url(pThis _this , char* da, size_t len){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("url = {} ", da[0..len]) ;
		HttpReq_Value* v	= &_this._Values[ _iUrl ] ;
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		return 0 ;
	}
	
	static extern(C) int _on_fragment(pThis _this , char* da, size_t len){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("fragment = {} ", da[0..len]) ;
		HttpReq_Value* v	= &_this._Values[ _iFragment] ;
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		return 0 ;
	}
	
	static extern(C) int _on_header_field(pThis _this , char* da, size_t len){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("header_field = {} ", da[0..len]) ;
		HttpReq_Value* v	= &_this._header_key ;
		if( _this._last_header_was_a_value ){
			HttpReq_Value* _v	= &_this._header_val ;
			// add key , val 
			char* key		= cast(char*) &_this.ih.slice[ v.i ] ;
			char* val		= cast(char*) &_this.ih.slice[ _v.i ] ;
			bool	isFind	= false ;
			foreach( int i ,  field ; HttpReq_Keys){
				if( field == key[0..v.l] ) {
					tango.stdc.string.memcpy(&_this._Values[i], _v, HttpReq_Value.sizeof) ;
					isFind	= true ;
					break ;
				}
			}
			if( !isFind ){
				log!(__FILE__, __LINE__)("{} = {} ",  key[0..v.l] , val[0.._v.l] ) ;
			}
			memset(&_this._header_key, 0,  HttpReq_Value.sizeof  );
			memset(&_this._header_val, 0,  HttpReq_Value.sizeof  );
		}
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		_this._last_header_was_a_value	= false ;
		return 0 ;
	}
	
	static extern(C) int _on_header_value(pThis _this , char* da, size_t len){
		version(DBG_PARSER)  log!(__FILE__, __LINE__)("header_value = {} ", da[0..len]) ;
		HttpReq_Value* v	= &_this._header_val ;
		if( v.i is 0 ){
			v.i	= da - cast(char*) &_this.ih.slice[0] ;
			v.l	= len ;
		} else {
			v.l	+= len ;
		}
		_this._last_header_was_a_value	= true ;
		return 0 ;
	}
	
	static extern(C) int _on_body(pThis _this , char* da, size_t len){
		version(DBG_PARSER)  log!(__FILE__, __LINE__)("body = {} ", da[0..len]) ;
		assert(_this._headers_complete);
		_this.ib( da[0..len]);
		return 0 ;
	}
	
	static extern(C) int _on_headers_complete(pThis _this){
		version(DBG_PARSER)  log!(__FILE__, __LINE__)("headers_complete  ") ;
		_this._headers_complete	= true ;
		return 0 ;
	}
	
	static extern(C) int _on_message_complete(pThis _this){
		version(DBG_PARSER) log!(__FILE__, __LINE__)("message_complete") ;
		_this.isLegal	= true ;
		return 0 ;
	}
}

template HttpState_Const(int code, char[] msg){
	mixin ("static const This " ~ msg ~ " = {`"~ ctfe_i2a(code) ~ "` , `" ~ msg ~ "` };" ) ;
}

struct HttpState {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;
	
	const char[]	code ;
	const char[]	msg ;
	
	mixin HttpState_Const!(100,  "CONTINUE" );
	mixin HttpState_Const!(200,  "OK" );
	mixin HttpState_Const!(300,  "MULTIPLE_CHOICES" );
	mixin HttpState_Const!(301,  "MOVED_PERMANENTLY" );
	mixin HttpState_Const!(302,  "FOUND" );
	mixin HttpState_Const!(303,  "SEE_OTHER" );
	mixin HttpState_Const!(304,  "NOT_MODIFIED" );
	mixin HttpState_Const!(307,  "TEMPORARY_REDIRECT" );
	mixin HttpState_Const!(400,  "BAD_REQUEST" );
	mixin HttpState_Const!(401,  "UNAUTHORIZED" );
	mixin HttpState_Const!(403,  "FORBIDDEN" );
	mixin HttpState_Const!(404,  "NOT_FOUND" );
	mixin HttpState_Const!(409,  "CONFLICT" );
	mixin HttpState_Const!(501,  "NOT_IMPLEMENTED" );
}

struct HttpRes {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;
	Berkeley*	soc ;
	Array		ob, oh ;
	HttpState*	state ;
	
	void init(Berkeley* _soc){
		soc	= _soc ;
		oh	= new Array(1024, 1024);
		ob	= new Array(1024, 1024);
		reset;
	}
	
	void send() {
		oh( HttpHeader.Version.value )(" ")( state.code )(" ")( state.msg  )( HttpConst.Eol ) ;
		oh( HttpHeader.ContentLength.value )(" ")( Int.toString( ob.slice.length)  )( HttpConst.Eol ) ;
		oh( HttpConst.Eol );
		
		soc.send( oh.slice);
		soc.send( ob.slice);
	}
	
	void reset(){
		state	= &HttpState.OK ;
		oh.clear ;
		ob.clear ;
	}
	
}